package comms;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Hashtable;

import logging.LogToFile;
import Vis.Visualiser;

/**
 * Sends/receives messages to the phone
 * 
 * @author norrielm
 *
 */
public class PhoneServer extends Thread {
	//Connection
	DatagramSocket serverSocket;
	private KinectClient client = null;
	private int port = 55555;
	//public boolean serverReady = false;
	private String serverAddr = "10.10.10.36";//"10.0.1.9"; //"192.168.1.3";//private or public
	//Log
	public  LogToFile log;
	//Vis
	private Visualiser v;
	private Hashtable<String,Device> devices = new Hashtable<String,Device>();

	public PhoneServer(Visualiser v){
		this.v=v;

		DatagramSocket serverSocket;
		try {
			serverSocket = new DatagramSocket(port, InetAddress.getByName(serverAddr));
			System.out.println("[Server] Binding to " +serverSocket.getLocalAddress().getHostAddress()+ ":" + serverSocket.getLocalPort());
			this.serverSocket = serverSocket;
		} catch (SocketException e) {
			e.printStackTrace();
		} catch (UnknownHostException e) {
			e.printStackTrace();
		}

		new Thread(this).start();
	}

	/* ************ Connect to phone *********/   

	@Override
	public void run() {
		System.out.println("[Server] starting conn...");
 
		//Start receiving from phone
		while(true){
			try {
				receiveFromPhone();
				Thread.sleep(1000);
			} catch (IOException e) {
				e.printStackTrace();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	/* ************ Send/Receive *********/

	public void receiveFromPhone() throws IOException{
		System.out.println("[Server] receive...");
		if(serverSocket == null || !serverSocket.isBound() || serverSocket.isClosed()){
			System.out.println("[Server] !bound or closed...");	
		}
		
		byte[] receiveData = new byte[2048];
		DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
		serverSocket.receive(receivePacket);
		
		String message = new String( receivePacket.getData()).toLowerCase();
		System.out.println("[Server] Received: " + message);

		/*
		 * Set the client address and port to reply to
		 * TODO: Cope with multiple phones by storing these 
		 */
		InetAddress ip = receivePacket.getAddress();
		String ipaddress = ip.toString();
		if(message.contains("close")){
			Device d = devices.get(ipaddress);
			if(d!=null){
				d.disconnect();
				client.phoneDisconnected();
				devices.remove(ipaddress);
			}
			return;
		}
		else if(devices.get(ipaddress)==null){
			System.out.println("Adding device with key " + ipaddress);
			devices.put(ipaddress, new Device(ip, receivePacket.getPort(), v));
			client.phoneConnected();
		}

		//deal with the message
		handlePhoneMessage(message, ip.toString());			
	}

	public void handlePhoneMessage(String phoneMessage, String ip) throws IOException{

		Device d = devices.get(ip);
		
		/* Connection established */
		if(!d.isConnected()){
			if((phoneMessage.contains("ack"))){				
				d.setConnected();
				System.out.println("[Server] device ready...");
				
				//setupPhonePanel(phoneMessage);
				d.setName(phoneMessage.substring(0,phoneMessage.lastIndexOf(" ")));
				d.panel().appendToPane("<< ["+phoneMessage+"]");
				log = new LogToFile("Exp-" + System.currentTimeMillis());
			}else{
				sendToDevice("connect\n", ip);
			}
			return;
		}
		d.panel().appendToPane(phoneMessage.trim());
		
		/* Connect */
		if(phoneMessage.contains("connect")){
			sendToDevice("connect\n", ip);	
		}

		PhoneInterface.commands c = PhoneInterface.handleKinectMessage(phoneMessage);
		switch(c){
		case ANGLES_APP:
				client.setupAnglesApp();
				break;
		case HOTSPOT_APP:
				client.setupHotspotApp();
				break;
				/* *** */
		case HOTSPOT:
				String text = phoneMessage.substring(phoneMessage.indexOf(" ", 
										phoneMessage.indexOf("hotspot")));
				System.out.println("add: " + text);
				//client.setupHotspotApp();
				client.hotspotApp.addHotspot(text);
				//START MAPA AUTO
				return;
		case LOG:
				synchronized(log){
					log.close();
					log = new LogToFile("Exp-" + System.currentTimeMillis());
				}
				System.out.println("new logfile created");
				break;
		/* *** */
		default:
			break;
		}

		/* Forward to Kinect */
		if(!(phoneMessage.contains("ack") || phoneMessage.contains("connect"))){
			System.out.println("[Server] Received: " + phoneMessage);
			if(client!=null){
				client.sendMessageToKinect(phoneMessage);
			}
		}	
	}

	public void append(String s, String ip){
		Device d = devices.get(ip);
		d.panel().appendToPane(s);
	}
	public void appendToAll(String s){
		for(Device d: devices.values()){
			d.panel().appendToPane(s);
		}
	}
	
	/**
	 * Send UDP packet to the phone
	 * @param s
	 * @throws IOException
	 */
	public void sendToAll(String s) throws IOException{
		for(Device d: devices.values()){
			sendToDevice(s, d.getIPAddress().toString());		
		}
	}
	
	/**
	 * Send UDP packet to the phone
	 * @param s
	 * @throws IOException
	 */
	public void sendToDevice(String s, String ipaddress) throws IOException{
		System.out.println("[Server] sending to device with key " + ipaddress + ": "+ s);
		final String ss=s.trim()+"\n";
		final String ip = ipaddress;
 
		new Thread(new Runnable(){
			@Override
			public void run() {
				Device d = devices.get(ip);
				
				byte[] sendData = new byte[512];
				sendData = ss.getBytes();
				DatagramPacket sendPacket =
					new DatagramPacket(sendData, sendData.length,
							d.getIPAddress(), d.getPort());
				try {
					serverSocket.send(sendPacket);
					Thread.sleep(20);
				} catch (IOException e) {
					e.printStackTrace();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			
			}
		}).start();
	}

	/**
	 * Tell device to log
	 */
	public void doLog(boolean b, String ip){
		try {
			this.sendToDevice("Log " + ((b)?"on":"off"), ip);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Fill packet, experimenting with android buffering
	 * @param length
	 * @param s
	 * @return
	 */
	public String longerString(int length, String s){
		for(int i=s.length(); i<length; i++){
			s+=" ";
		}
		return s;
	}

	/* ******** CLIENT *******/
	public void forwardMessageToClient(String s){
		System.out.println("[Server] Forwarding to Kinect: " + s);
		while(client==null){			
		}
		client.sendMessageToKinect(s);
	}

	public void setClient(KinectClient client) {
		this.client = client;
	}

	public KinectClient getClient() {
		return client;
	}

	/**
	 * Busy wait for client connection: Should use wait/notify
	 */
	public void waitForClientConn(){
		//wait till client object has been made in the communicator
		while(client==null){			
		}
		//wait for client to connect to the kinect
		while(!client.clientAlive){
		}
	}
}